package br3akr.DeadlyDinnerParty.Server.World;

import java.util.Date;
import java.util.HashSet;
import java.util.Observable;
import java.util.Random;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import br3akr.DeadlyDinnerParty.Server.Actions.ActionResponse;
import br3akr.DeadlyDinnerParty.Server.Engine.Session;
import br3akr.DeadlyDinnerParty.Server.Engine.TimePassTask;
import br3akr.DeadlyDinnerParty.Server.Exceptions.DeadlyDinnerPartyException;
import br3akr.DeadlyDinnerParty.Server.World.Exit.ExitStatus;

public class World extends Observable {
	public enum Time {
		EARLY_DAY("Early-Day", "The Sun sets low on the Eastern horizon. It is morning."), 
		MID_DAY("Mid-Day", "The Sun sets high in the sky. It is noon."),
		LATE_DAY("Late-Day", "The Sun sets low on the Western horizon. It is now the afternoon"), 
		EARLY_EVENING("Early-Evening", "The sky grows dark. It is now early-evening."), 
		MID_EVENING("Mid-Evening", "It is pitch black. It is now mid-evening."),
		LATE_EVENING("Late-Evening", "The Night is nearly over, hints of a glowing sky on the eastern horizon. It is late-evening.");
		Time(String timeName, String description) {
			this.timeName = timeName;
			this.description = description;
		}
		private String description;
		private String timeName;
		public Time getNextTime() {
			if (this == EARLY_DAY) {
				return MID_DAY;
			} else if (this == MID_DAY) {
				return LATE_DAY;
			} else if (this == LATE_DAY) {
				return EARLY_EVENING;
			} else if (this == EARLY_EVENING) {
				return (MID_EVENING);
			} else if (this == MID_EVENING) {
				return (LATE_EVENING);
			} else {
				return (EARLY_DAY);
			}
		}
		public String getDescription() {
			return description;
		}
		public String getName() {
			return timeName;
		}
	}
	private Area[] areasArray;
	private Set<Area> areas;
	private Set<Session> onlinePlayers;
	private Time currentTime;
	private boolean enoughPlayers;
	private Set<Key> spareKeys;
	public static final int REQUIRED_NUMBER_OF_PLAYERS = 3;
	
	public World() {
		areas = new HashSet<Area>();
		areasArray = new Area[20];
		String[] secretDoorActorDescs = new String[20];
		String[] secretDoorOnlookerDescs = new String[20];
		
		areasArray[0] = new Area("Private Room One", 
				"This small but comfortable room has a desk, a chair, a small lamp, and a feather bed.");
		secretDoorActorDescs[0] = "You yank on the rug and from underneath it is revealed a small trapped door. You open the trapped door and enter the hidden halls.";
		secretDoorOnlookerDescs[0] = "yanks on the rug and from underneath it is revealed a small trapped door. You open the trapped door and enter the hidden halls.";
		areas.add(areasArray[0]);
		
		areasArray[1] = new Area("Private Room Two", 
				"This small but comfortable room has a desk, a chair, a small lamp, and a feather bed.");
		secretDoorActorDescs[1] = "You pull on one of the posts of the bed, and the desk slides back revealing The Hidden Halls, you enter.";
		secretDoorOnlookerDescs[1] = "pulls on one of the posts of the bed, and the desk slides back revealing The Hidden Halls, they enter.";
		areas.add(areasArray[1]);
		
		areasArray[2] = new Area("Private Room Three", 
				"This small but comfortable room has a desk, a chair, a small lamp, and a feather bed.");
		secretDoorActorDescs[2] = "You pull on one of the posts of the bed, and the desk slides back revealing The Hidden Halls, you enter.";
		secretDoorOnlookerDescs[2] = "pulls on one of the posts of the bed, and the desk slides back revealing The Hidden Halls, they enter.";
		areas.add(areasArray[2]);
		
		areasArray[3] = new Area("The Front Desk", 
				"This area of the inn is the primary lobby where patrons enter. A bronze coatrack with a matching shoe rack set against one wall, and a counter where the innkeep has spent most of his time is on the opposite.");
		secretDoorActorDescs[3] = "A swift pull one one of the arms of the coat rack reveals a hidden door behind the desk, you enter.";
		secretDoorOnlookerDescs[3] = "swiftly pulls one one of the arms of the coat rack reveals a hidden door behind the desk, they enter.";
		areas.add(areasArray[3]);
		
		areasArray[4] = new Area("The Private Bedrooms Hall", 
				"This long hall acts as the nexus from which all the private rooms connect. Old paintings of the city outside line the walls, and a small flowerpot sits comfortably atop an intrecately carved stand.");
		secretDoorActorDescs[4] = "You slide back one of the paintings, and pull on a nearly invisible lever. A small door appears and you enter.";
		secretDoorOnlookerDescs[4] = "slides back one of the paintings, and pulls on a nearly invisible lever. A small door appears and they enter.";
		areas.add(areasArray[4]);
		
		areasArray[5] = new Area("The Commons", 
				"This common room contains all of the beds, and most of the furniture common guests use. Each neatly tucked bed has at the foot a small chest for their personal belongings.");
		secretDoorActorDescs[5] = "\"One, two, three four... the fourth bed.\" you mutter to yourself as you pace down the commons. You then grab the two stout posts of the bed and push hard on them. The bed collapses backwards and you tumble into the secret halls.";
		secretDoorOnlookerDescs[5] = "mutters to themselves \"One, two, three four... the fourth bed.\" as they pace down the commons. They then grab the two stout posts of the bed and push hard on them. The bed collapses backwards and they tumble into the secret halls.";
		areas.add(areasArray[5]);
		
		areasArray[6] = new Area("The Dining Room", 
				"Two massive china cabinets are filled to the brim with fine china. Many comfortable silk lined chairs with luxurious arms round the table.");
		secretDoorActorDescs[6] = "\"Psht, the cookoo clock of course!\" You exclaim to yourself as you yank on one of the chains and gasp as the floor beneath you collapses open, sucking you down into the secret halls.";
		secretDoorOnlookerDescs[6] = "exclaims \"Psht, the cookoo clock of course!\" as they yank on one of the chains and gasp as the floor beneath them collapses open, sucking them down into the secret halls.";
		areas.add(areasArray[6]);
		
		areasArray[7] = new Area("Private Room Four", 
				"This small but comfortable room has a desk, a chair, a small lamp, and a feather bed.");
		secretDoorActorDescs[7] = "You yank on the rug and from underneath it is revealed a small trapped door. You open the trapped door and enter the hidden halls.";
		secretDoorOnlookerDescs[7] = "yanks on the rug and from underneath it is revealed a small trapped door. You open the trapped door and enter the hidden halls.";
		areas.add(areasArray[7]);
		
		areasArray[8] = new Area("Private Room Five", 
				"This small but comfortable room has a desk, a chair, a small lamp, and a feather bed.");
		secretDoorActorDescs[8] = "You yank on the rug and from underneath it is revealed a small trapped door. You open the trapped door and enter the hidden halls.";
		secretDoorOnlookerDescs[8] = "yanks on the rug and from underneath it is revealed a small trapped door. You open the trapped door and enter the hidden halls.";
		areas.add(areasArray[8]);
		
		areasArray[9] = new Area("The Wine Cellar", 
				"This cool area of the building is generally reserved for the innkeep on his bad days.");
		secretDoorActorDescs[9] = "You yank the face off of one of the hollow kegs laying on their sides and climb in. The area suddenly opens up as you find yourself in the secret halls.";
		secretDoorOnlookerDescs[9] = "yanks the face off of one of the hollow kegs laying on their sides and climb in. The area suddenly opens up as you find yourself in the secret halls.";
		areas.add(areasArray[9]);
		
		areasArray[10] = new Area("The Secret Hall", 
				"An eerilie tight coridor winds mindlessly through the walls of the inn. It's impossible to tell where either exit will spill you out.");
		areas.add(areasArray[10]);
		
		spareKeys = new HashSet<Key>();
		
		Key areaKey = new Key("Private Room One Key", "A lovely brass key, with a stylized one etched in it. This key is used for both the room, and the chest in the room.", null, null, null, false);
		Chest c = new Chest("Dragon", "A beautifully handcrafted cherry wood chest with a dragon etched in the top.");
		Exit exit = new Exit(areasArray[0], Exit.ExitDirections.SOUTHEAST, areasArray[4], Exit.ExitDirections.NORTHWEST);
		exit.setStatus(ExitStatus.LOCKED);
		areaKey.addChestLock(c);
		areaKey.addDoorLock(exit);
		spareKeys.add(areaKey);
		areasArray[0].addChest(c);
		areasArray[0].addExit(exit, exit.getExitDirection(areasArray[0]));
		areasArray[4].addExit(exit, exit.getExitDirection(areasArray[4]));
		
		c = new Chest("Lion", "A beautifully handcrafted cherry wood chest with a lion etched in the top.");
		areaKey = new Key("Private Room Two Key", "A lovely brass key, with a stylized two etched in it. This key is used for both the room, and the chest in the room.", null, null, null, false);
		exit = new Exit(areasArray[1], Exit.ExitDirections.SOUTH, areasArray[4], Exit.ExitDirections.NORTH);
		exit.setStatus(ExitStatus.LOCKED);
		areaKey.addChestLock(c);
		areaKey.addDoorLock(exit);
		spareKeys.add(areaKey);
		areasArray[1].addChest(c);
		areasArray[1].addExit(exit, exit.getExitDirection(areasArray[1]));
		areasArray[4].addExit(exit, exit.getExitDirection(areasArray[4]));
		
		c = new Chest("Tiger", "A beautifully handcrafted cherry wood chest with a tiger etched in the top.");
		areaKey = new Key("Private Room Three Key", "A lovely brass key, with a stylized three etched in it. This key is used for both the room, and the chest in the room.", null, null, null, false);
		exit = new Exit(areasArray[2], Exit.ExitDirections.SOUTHWEST, areasArray[4], Exit.ExitDirections.NORTHEAST);
		exit.setStatus(ExitStatus.LOCKED);
		areaKey.addChestLock(c);
		areaKey.addDoorLock(exit);
		spareKeys.add(areaKey);
		areasArray[2].addChest(c);
		areasArray[2].addExit(exit, exit.getExitDirection(areasArray[2]));
		areasArray[4].addExit(exit, exit.getExitDirection(areasArray[4]));
		
		exit = new Exit(areasArray[3], Exit.ExitDirections.UP, areasArray[4], Exit.ExitDirections.DOWN);
		exit.setStatus(ExitStatus.CLOSED);
		areasArray[3].addExit(exit, exit.getExitDirection(areasArray[3]));
		areasArray[4].addExit(exit, exit.getExitDirection(areasArray[4]));
		
		exit = new Exit(areasArray[3], Exit.ExitDirections.SOUTHWEST, areasArray[5], Exit.ExitDirections.NORTHEAST);
		exit.setStatus(ExitStatus.CLOSED);
		areasArray[3].addExit(exit, exit.getExitDirection(areasArray[3]));
		areasArray[5].addExit(exit, exit.getExitDirection(areasArray[5]));
		
		exit = new Exit(areasArray[3], Exit.ExitDirections.SOUTHEAST, areasArray[6], Exit.ExitDirections.NORTHWEST);
		exit.setStatus(ExitStatus.CLOSED);
		areasArray[3].addExit(exit, exit.getExitDirection(areasArray[3]));
		areasArray[6].addExit(exit, exit.getExitDirection(areasArray[6]));
		
		exit = new Exit(areasArray[5], Exit.ExitDirections.EAST, areasArray[6], Exit.ExitDirections.WEST);
		exit.setStatus(ExitStatus.CLOSED);
		areasArray[5].addExit(exit, exit.getExitDirection(areasArray[5]));
		areasArray[6].addExit(exit, exit.getExitDirection(areasArray[6]));
		
		exit = new Exit(areasArray[6], Exit.ExitDirections.DOWN, areasArray[9], Exit.ExitDirections.UP);
		exit.setStatus(ExitStatus.CLOSED);
		areasArray[6].addExit(exit, exit.getExitDirection(areasArray[6]));
		areasArray[9].addExit(exit, exit.getExitDirection(areasArray[9]));

		c = new Chest("One", "A large wooden chest with a stylized one carved in the top.");
		areasArray[5].addChest(c);
		c = new Chest("Two", "A large wooden chest with a stylized two carved in the top.");
		areasArray[5].addChest(c);
		c = new Chest("Three", "A large wooden chest with a stylized three carved in the top.");
		areasArray[5].addChest(c);
		c = new Chest("Four", "A large wooden chest with a stylized four carved in the top.");
		areasArray[5].addChest(c);
		c = new Chest("Five", "A large wooden chest with a stylized five carved in the top.");
		areasArray[5].addChest(c);
		c = new Chest("Six", "A large wooden chest with a stylized six carved in the top.");
		areasArray[5].addChest(c);
		c = new Chest("Seven", "A large wooden chest with a stylized seven carved in the top.");
		areasArray[5].addChest(c);
		c = new Chest("Eight", "A large wooden chest with a stylized eight carved in the top.");
		areasArray[5].addChest(c);
		c = new Chest("Nine", "A large wooden chest with a stylized nine carved in the top.");
		areasArray[5].addChest(c);
		c = new Chest("Ten", "A large wooden chest with a stylized ten carved in the top.");
		areasArray[5].addChest(c);

		areaKey = new Key("Basement Key", "A plain brass key, with a stylized keg etched in it.", null, null, null, false);
		spareKeys.add(areaKey);
		
		c = new Chest("Bear", "A beautifully handcrafted cherry wood chest with a bear etched in the top.");
		areasArray[7].addChest(c);
		exit = new Exit(areasArray[7], Exit.ExitDirections.NORTHEAST, areasArray[4], Exit.ExitDirections.SOUTHWEST);
		exit.setStatus(ExitStatus.LOCKED);
		areaKey = new Key("Private Room Four Key", "A lovely brass key, with a stylized four etched in it. This key is used for both the room, and the chest in the room.", null, null, null, false);
		areaKey.addChestLock(c);
		areaKey.addDoorLock(exit);
		spareKeys.add(areaKey);
		areasArray[7].addExit(exit, exit.getExitDirection(areasArray[7]));
		areasArray[4].addExit(exit, exit.getExitDirection(areasArray[4]));
		
		c = new Chest("Wolf", "A beautifully handcrafted cherry wood chest with a wolf etched in the top.");
		areasArray[8].addChest(c);
		exit = new Exit(areasArray[8], Exit.ExitDirections.NORTHWEST, areasArray[4], Exit.ExitDirections.SOUTHEAST);
		areaKey = new Key("Private Room Five Key", "A lovely brass key, with a stylized five etched in it. This key is used for both the room, and the chest in the room.", null, null, null, false);
		areaKey.addChestLock(c);
		areaKey.addDoorLock(exit);
		spareKeys.add(areaKey);
		exit.setStatus(ExitStatus.LOCKED);
		areasArray[8].addExit(exit, exit.getExitDirection(areasArray[8]));
		areasArray[4].addExit(exit, exit.getExitDirection(areasArray[4]));
		
		Random r = new Random();
		int secret1 = r.nextInt(9) + 1; 
		int secret2 = r.nextInt(9) + 1;
		while (secret1 == secret2) {
			secret2 = r.nextInt(9) + 1;
		}

		System.out.println("Adding a secret area to: " + areasArray[secret1].getName());
		System.out.println("Adding a secret area to: " + areasArray[secret2].getName());
		
		exit = new Exit(areasArray[10], Exit.ExitDirections.WEST, "An ancient carved door.", "An ancient carved door.", 
						areasArray[secret1], Exit.ExitDirections.SECRET, secretDoorActorDescs[secret1], secretDoorOnlookerDescs[secret1], 
						true);
		exit.setStatus(Exit.ExitStatus.OPEN);
		areasArray[10].addExit(exit, Exit.ExitDirections.WEST);
		areasArray[secret1].addExit(exit, Exit.ExitDirections.SECRET);
		
		exit = new Exit(areasArray[10], Exit.ExitDirections.WEST, "A new carved door.", "A new carved door.", 
				areasArray[secret2], Exit.ExitDirections.SECRET, secretDoorActorDescs[secret2], secretDoorOnlookerDescs[secret2], 
				true);
		exit.setStatus(Exit.ExitStatus.OPEN);
		areasArray[10].addExit(exit, Exit.ExitDirections.EAST);
		areasArray[secret2].addExit(exit, Exit.ExitDirections.SECRET);
		
		onlinePlayers = new HashSet<Session>();

		currentTime = Time.EARLY_DAY;
		TimerTask task = new TimePassTask(this);
		Timer timer = new Timer();
		timer.scheduleAtFixedRate(task, new Date(), 300000);
	}
	
	public void addOnlinePlayer(Session session) {
		onlinePlayers.add(session);
		System.out.println("Online players: " + onlinePlayers.size());
		addObserver(session);
		if (onlinePlayers.size() >= REQUIRED_NUMBER_OF_PLAYERS && !enoughPlayers) {
			notifyWorld("Hey everybody, we have enough people now!");
			createCharacters();
			enoughPlayers = true;
		}
	}
	
	public void removeOnlinePlayer(Session session) {
		onlinePlayers.remove(session.getPlayerName());
	}
	
	public Area getArea(int locId) {
		if (locId > areasArray.length - 1 || locId < 0) {
			throw new IllegalArgumentException("The world size is currently " + areasArray.length + ". You cannot enter an area greater than that, or lower than 0.");
		}
		return areasArray[locId];
	}
	
	public Area getArea(String name) throws DeadlyDinnerPartyException {
		Area retval = null;
		for (int i = 0; i < areasArray.length; i++) {
			Area curArea = areasArray[i]; 
			if (curArea != null && curArea.getName().toUpperCase().indexOf(name.toUpperCase()) >= 0) {
				if (retval == null) {
					retval = curArea;
				} else {
					throw new DeadlyDinnerPartyException("Which area?");
				}
			}
		}
		return retval;
	}
	
	public Time getTime() {
		return currentTime;
	}
	
	public Set<Session> getOnlinePlayers() {
		return onlinePlayers;
	}

	public void setTime(Time newTime) {
		currentTime = newTime;
		notifyWorld(currentTime.getDescription());
	}
	
	public void notifyWorld(String notificationMessage) {
		if (onlinePlayers.size() > 0) {
			ActionResponse notification = new ActionResponse();
			notification.setSystemMessage(notificationMessage);
			setChanged();
			notifyObservers(notification);
		}
	}
	
	public void broadcastAction(ActionResponse actionResponse) {
		setChanged();
		notifyObservers(actionResponse);
	}
	
	private void createCharacters() {
		if (onlinePlayers.size() > 0) {
			for (Session ses : onlinePlayers) {
				ses.createCharacter();
			}
		}
	}
	
	public boolean haveEnoughPlayers() {
		return enoughPlayers;
	}
	
	public int getNumberOfPlayers() {
		return onlinePlayers.size();
	}
	
	public Set<Area> getAreas() {
		return areas;
	}
	
	public Key getSpareKey(String name) {
		for (Key k : spareKeys) {
			if (k.getName().equalsIgnoreCase(name)) {
				return k;
			}
		}
		return null;
	}
	
	public Key removeKey(String name) {
		for (Key k : spareKeys) {
			if (k.getName().equalsIgnoreCase(name)) {
				spareKeys.remove(k);
				return k;
			}
		}
		return null;
	}
	
	public Session getSessionByCharacter(GameCharacter gc) {
		for (Session s : onlinePlayers) {
			if (s.getCharacter().equals(gc)) {
				return s;
			}
		}
		
		return null;
	}
}
